package logic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

import com.rits.cloning.Cloner;

public class Sudoku {

	ArrayList<Constraint> sudokusConstraints = new ArrayList<>();
	cell[][] cells = new cell[9][9];
	ArrayList<Integer> possibleVlaues = new ArrayList<Integer>();
	Cloner cloner = new Cloner();
	public static final int EASY = 0;
	public static final int Moderate = 1;
	private int difficulty;
	
	public Sudoku(int difficulty)
	{
		this.difficulty = difficulty;
		
		for(int i = 1; i < 10; i++)			
		{
			possibleVlaues.add(i);
		}
		
		for(int x = 0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)
			{
				cells[x][y] = new cell(cloner.deepClone(possibleVlaues));
			}
		}
		
		Constraint newConstraint = new Constraint();
		cell curentCell;
		
		// for each Block
		Constraint[][] sudokuBlocks = new Constraint[3][3];
		for(int n =0; n < 9;n++)
		{
			newConstraint = new Constraint();
			sudokusConstraints.add(newConstraint);
			sudokuBlocks[n / 3][n % 3] = newConstraint;
		}
		
		for(int y = 0; y < 9;y++)
		{
			for(int x = 0; x < 9; x++)
			{
				int xBlock = x / 3;
				int yBlock = y / 3;
				
				newConstraint = sudokuBlocks[xBlock][yBlock];
				curentCell = cells[x][y];
				newConstraint.addCell(curentCell);
				curentCell.addConstraint(newConstraint);
			}
		}
		
		// for each column
		for(int x = 0; x < 9;x++)
		{
			newConstraint = new Constraint();
			for(int y = 0; y < 9; y++)
			{
				curentCell = cells[x][y];
				newConstraint.addCell(curentCell);
				curentCell.addConstraint(newConstraint);
			}
		}
		
		// for each row
		for(int y = 0; y < 9;y++)
		{
			newConstraint = new Constraint();
			for(int x = 0; x < 9; x++)
			{
				curentCell = cells[x][y];
				newConstraint.addCell(curentCell);
				curentCell.addConstraint(newConstraint);
			}
		}
		
		randomIntialSeed();

		while(reducePuzzlel());
	}
	
	/**
	 * generates a random completed sudoku puzzle 
	 */
	private void randomIntialSeed()
	{	
		cell currentCell;

		for(int x = 0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)		
			{
				Collections.shuffle(cells[x][y].getPossilbeValues());
			}
		}
		
		for(int x = 0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)		
			{
				currentCell = cells[x][y];
				int n;
				if(currentCell.getCurrentValue() != cell.BLANK)
				{
					n = currentCell.getPossilbeValues().indexOf(currentCell.getCurrentValue());
					n++;
					if( n >= currentCell.getPossilbeValues().size())							
					{
						currentCell.setCurrentValue(cell.BLANK);
						if(y != 0 )
						{
							y = y -2;
						}
						else
						{
							x = x -1;
							y = 7;
						}
						continue;						
					}
				}
				else
				{
					n = 0;
				}
					
				currentCell.setCurrentValue(currentCell.getPossilbeValues().get(n));
				while(!isCellValueAcceptable(currentCell))
				{
					n++;
					if( n >= currentCell.getPossilbeValues().size())							
					{
						currentCell.setCurrentValue(cell.BLANK);
						if(y != 0 )
						{
							y = y -2;
						}
						else
						{
							x = x - 1;
							y = 7;
						}
						break;							
					}
					currentCell.setCurrentValue(currentCell.getPossilbeValues().get(n));
				}
			}				
		}
		updatePossibleValuesAll();
	}
	
	
	
	/**
	 * for updating possible values after seed is created
	 */
	private void updatePossibleValuesAll()
	{
		for( int x = 0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)
			{
				if(cells[x][y].getCurrentValue() != cell.BLANK)
				{
					ArrayList<Integer> list = new ArrayList<Integer>();
					list.add(cells[x][y].getCurrentValue());
					cells[x][y].setPossiblevalues(list);
				}
			}
		}
	}
	
	/**
	 * sets on cells value to blank if one exists that can be found logically
	 * @return true if a cell was found to blank
	 */
	private boolean reducePuzzlel()
	{
		boolean result = true;
		ArrayList<Integer[]> guess = new ArrayList<Integer[]>();
		for(int x = 0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)
			{
				guess.add(new Integer[]{x,y});
			}
		}
		
		Random randNumGen = new Random();
		boolean removed = false;
		while(!removed && guess.size() > 0)
		{
			Integer[] cellToremove;
			cellToremove = guess.get(randNumGen.nextInt(guess.size()));
			
			cell celltoremove = this.cells[cellToremove[0]][cellToremove[1]];
			if(cellCanBeDerivedLogicly(celltoremove) && celltoremove.getCurrentValue() != cell.BLANK)
			{
				celltoremove.setCurrentValue(cell.BLANK);
				
				removed = true;
			}
			else			
			{
				guess.remove(cellToremove);
			}
		}
		if(guess.size() == 0)
		{
			result = false;
		}
		return result;
		
	}
	
	/**
	 * tests to see if a cells value can be found using logic. logic used is based on difficulty instance variable 
	 * @param cellToTest
	 * @return true if cellToTest's value can be derived with this sudokus difficulty level
	 */
	public boolean cellCanBeDerivedLogicly(cell cellToTest)
	{
		ArrayList<Integer> possiblValues = cloner.deepClone(this.possibleVlaues);
		boolean result = false;
		
		switch (difficulty)
		{
			case Sudoku.Moderate:		
				break;

				default: // easy
				// naked singles (only place value can go)
				for(Constraint constraint:cellToTest.getConstraints())
				{
					for(cell cell:constraint.getCells())
					{
						int index = possiblValues.indexOf(cell.getCurrentValue());
						if(cellToTest != cell && index != -1)
						{
							possiblValues.remove(index);
						}
					}
				}
				if(possiblValues.size() > 1)					
				{
					// look for values not possible else where 
					int value = cellToTest.getCurrentValue();
					cellToTest.setCurrentValue(cell.BLANK);
					easyUpdatePossibleValues();
					for(Constraint constraint:cellToTest.getConstraints())
					{
						ArrayList<Integer> notPossibleElseWhere = cloner.deepClone(this.possibleVlaues);
						for(cell cell:constraint.getCells())
						{
							if(cellToTest != cell)
							{
								notPossibleElseWhere.removeAll(cell.getPossilbeValues());
							}
						}
						if(notPossibleElseWhere.size() == 1)
						{
							possiblValues = notPossibleElseWhere;
							break;
						}
					}
					cellToTest.setCurrentValue(value);
				}
				break;
		} 
			
		if(possiblValues.size() == 1)
		{
			result = true;
		}
		
		return result;
	}
	
	/**
	 * update possible values for easy difficulty 
	 */
	private void easyUpdatePossibleValues()
	{
		for(int x =0; x < 9; x++)
		{
			for(int y = 0; y < 9; y++)
			{
				if(cells[x][y].getCurrentValue() == cell.BLANK)
				{
					ArrayList<Integer> possiblValues = cloner.deepClone(this.possibleVlaues);
					for(Constraint constraint:(cells[x][y].getConstraints()))
					{
						for(cell cell:constraint.getCells())
						{
							int index = possiblValues.indexOf(cell.getCurrentValue());
							if(cells[x][y] != cell && index != -1)
							{
								possiblValues.remove(index);
							}
						}
					}
					cells[x][y].setPossiblevalues(possiblValues);
				}
				else // if cell has a value then it will be its only possible value at this stage 				
				{
					cells[x][y].setPossiblevalues(new ArrayList<Integer>());
					cells[x][y].addPossibleValue(cells[x][y].getCurrentValue());
				}
			}
		}
	}
	
	/**
	 * tests a cell to see if its current value violates any constraints 
	 * @param cellTotest 
	 * @return true if none of the cells constraints are violated
	 */
	public boolean isCellValueAcceptable(cell cellTotest)
	{
		for(Constraint constraint:cellTotest.getConstraints())
		{
			if(constraint.violated())
			{
				return false;
			}
		}
		return true;
	}
	
	public cell[][] getCells()
	{
		return this.cells;
	}
	
	/**
	 * test if all constraints are satisfied if they are returns true
	 * @return
	 */
	public boolean isSolved()
	{
		boolean result = true;
		for(Constraint constaint:this.sudokusConstraints)
		{
			if(!constaint.satisfied())
			{
				result = false;
			}
		}
		
		return result;
	}
}
